// NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


//
// Includes
//
//#include "login_service.h"

//#include <nel/misc/types_nl.h>
#include <game_share/game_def.h>
//#include <math.h>
//#include <stdio.h>
//#include <ctype.h>

#include <map>
#include <vector>

#include <nel/misc/log.h>
#include <nel/misc/debug.h>
#include <nel/misc/displayer.h>
#include <nel/misc/config_file.h>

#include <nel/net/service.h>
#include <nel/net/login_cookie.h>
#include <game_share/object_pool.h>
#include <server_share/server_def.h>

#include "login_mgr.h"
#include "fes_mgr.h"

using namespace std;
using namespace NLMISC;
using namespace NLNET;
using namespace DEF;
using namespace LS;

static CCallbackServer *ClientsServer = NULL;

inline void sendToClient(CMessage &msgout, TSockId sockId)
{
	nlassert(ClientsServer != 0);
	ClientsServer->send(msgout, sockId);
}

static void cbClientVerifyLoginPassword(CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
{
	if ( LoginMgr.removeLoginMsgTimeOut(from) )
	{
		if ( LoginMgr.isFull() )
		{
			sendERR( SERVER_FULL, from, ClientsServer );
			return;
		}

		string user;
		string password;
		uint32 version;

		try
		{
			msgin.serial (user);
			msgin.serial (password);
			msgin.serial (version);
		}
		catch (Exception &)
		{
			netbase.disconnect(from);
			nlwarning ("Error recv msg ClientVerifyLoginPassword.");
			return;
		}

		SLoginRequest* pLoginRequest = LoginMgr.findUser(user);
		if ( pLoginRequest != NULL )
		{
			netbase.disconnect(from);
			return;
		}

		pLoginRequest = new SLoginRequest;
		if ( pLoginRequest == NULL )
		{
			sendERR( SERVER_FULL, from, ClientsServer );
			return;
		}

		pLoginRequest->user     = user;
		pLoginRequest->password = password;
		pLoginRequest->state    = VERIFY_CHECKING;
		pLoginRequest->sockId   = from;

		if ( !LoginMgr.push( pLoginRequest ) )
		{
			sendERR( SERVER_FULL, from, ClientsServer );
			delete pLoginRequest;
			pLoginRequest = NULL;
		}
	}
}


static void cbClientConnection (TSockId from, void *arg)
{
	CCallbackNetBase *cnb = ClientsServer;
	const CInetAddress &ia = cnb->hostAddress (from);
	nldebug("new client connection: %s", ia.asString ().c_str ());
//	Output->displayNL ("CCC: Connection from %s", ia.asString ().c_str ());
	cnb->authorizeOnly ("VLP", from);
	LoginMgr.addLoginMsgTimeOut(from);
}

static void cbClientDisconnection (TSockId from, void *arg)
{
	//CCallbackNetBase *cnb = ClientsServer;
	//const CInetAddress &ia = cnb->hostAddress (from);
	//nldebug("new client disconnection: %s", ia.asString ().c_str ());
	LoginMgr.removeSock(from);
	LoginMgr.removeLoginMsgTimeOut(from);
}

const TCallbackItem ClientCallbackArray[] =
{
	{ "VLP",      cbClientVerifyLoginPassword },	//	from client
	
};

static void cbFesInfo (CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
	MSG_STR::CFrontEnd fes;
	msgin.serial(fes);
	FesMgr.addFes(fes);
}

static void cbFesCurrPlayers (CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
	MSG_STR::CFrontEnd* pFes = FesMgr.findFes(sid);
	if ( pFes != NULL )
	{
		msgin.serial(pFes->CurrPlayers);
		FesMgr.resetCurrPlayers();
	}
}

static void cbFesClientSession (CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
	string user;
	msgin.serial(user);
	TTicks session;
	msgin.serial(session);

	SLoginRequest* pLoginRequest = LoginMgr.findUser(user);
	if ( pLoginRequest != NULL )
	{
		MSG_STR::CFrontEnd* pFrontEnd = FesMgr.findFes(sid);
		if ( pFrontEnd != NULL )
		{
			CMessage msgout("SC");
			msgout.serial(pFrontEnd->Ip);
			msgout.serial(pFrontEnd->Port);
			msgout.serial(session);
				
			sendToClient( msgout, pLoginRequest->sockId );
			ClientsServer->disconnect(pLoginRequest->sockId);
		}
		else
		{
			sendERR( DEF::SERVER_NOT_OPEN, pLoginRequest->sockId, ClientsServer );
		}
		LoginMgr.removeUser(user);
	}
}


static void cbLoginOK(CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
	UID uid;
	msgin.serial(uid);

	SLoginRequest* pLoginRequest = LoginMgr.findUser(uid);

	if ( pLoginRequest != NULL )
	{
		MSG_STR::CFrontEnd* pFrontEnd = FesMgr.findBestFes();
		if ( pFrontEnd == NULL )
		{
			sendERR( DEF::SERVER_FULL, pLoginRequest->sockId, ClientsServer );
			return;
		}

		++(pFrontEnd->CurrPlayers);

		///  get session
		CMessage msg_connect_fes("GET_SESSION");
		msg_connect_fes.serial(uid);
		msg_connect_fes.serial(pLoginRequest->user);
		CUnifiedNetwork::getInstance()->send(pFrontEnd->SId, msg_connect_fes);
	}
}

static const TUnifiedCallbackItem FESCallbackArray[] =
{
	{ "SET_FES_INFO",			cbFesInfo },
	{ "SET_FES_PLAYERS",		cbFesCurrPlayers },
	{ "SET_SESSION",			cbFesClientSession },
	{ "LOGIN_OK",               cbLoginOK },						//	from EGS
	
};

void cbFESConnection (const std::string &serviceName, TServiceId  sid, void *arg)
{
	CMessage        msgout("GET_FES_INFO");
	//NLNET::CUnifiedNetwork::getInstance()->send (sid, msgout);
}

void cbFESDisConnection (const std::string &serviceName, TServiceId  sid, void *arg)
{
	FesMgr.removeFes(sid);
}

void connectionClientInit ()
{
	nlassert(ClientsServer == NULL);

	LoginMgr.init( );

	ClientsServer = new CCallbackServer();
	nlassert(ClientsServer != NULL);
	LoginMgr.CallbackServer(ClientsServer);

	uint16 port = (uint16) IService::getInstance ()->ConfigFile.getVar ("ClientsPort").asInt();
	ClientsServer->init (port);

	ClientsServer->addCallbackArray(ClientCallbackArray, sizeof(ClientCallbackArray)/sizeof(ClientCallbackArray[0]));
	ClientsServer->setConnectionCallback(cbClientConnection, 0);
	ClientsServer->setDisconnectionCallback(cbClientDisconnection, 0);

	CUnifiedNetwork::getInstance ()->setServiceUpCallback ("FES", cbFESConnection, NULL);
	CUnifiedNetwork::getInstance()->setServiceDownCallback("FES", cbFESDisConnection, NULL);
	// catch the messages from Frontend Service to know if the user can connect or not
	CUnifiedNetwork::getInstance ()->addCallbackArray (FESCallbackArray, sizeof(FESCallbackArray)/sizeof(FESCallbackArray[0]));
}

void connectionClientUpdate ()
{
	nlassert(ClientsServer != NULL);

	try
	{
		ClientsServer->update();
		LoginMgr.update();
	}
	catch (Exception &e)
	{
		nlwarning ("Error during update: '%s'", e.what ());
	}
}

void connectionClientRelease ()
{
	nlassert(ClientsServer != NULL);

	delete ClientsServer;
	ClientsServer = NULL;
}
